# -*- coding: utf-8 -*-
#
# Picard, the next-generation MusicBrainz tagger
#
# Copyright (C) 2006-2007 Lukáš Lalinský
# Copyright (C) 2008 Gary van der Merwe
# Copyright (C) 2011-2013 Michael Wiencek
# Copyright (C) 2013, 2018, 2020-2024 Laurent Monin
# Copyright (C) 2016 Sambhav Kothari
# Copyright (C) 2017 Sophist-UK
# Copyright (C) 2018 Vishal Choudhary
# Copyright (C) 2020, 2022 Philipp Wolfer
# Copyright (C) 2022 Bob Swift
# Copyright (C) 2022 skelly37
# Copyright (C) 2024 Giorgio Fontanive
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.


import sys
import threading
import time
import traceback

from PyQt6.QtCore import (
    QCoreApplication,
    QEvent,
    QRunnable,
)

from picard import log


class ProxyToMainEvent(QEvent):
    def __init__(self, func, *args, **kwargs):
        super().__init__(QEvent.Type.User)
        self.func = func
        self.args = args
        self.kwargs = kwargs

    def run(self):
        self.func(*self.args, **self.kwargs)


class Runnable(QRunnable):
    def __init__(self, func, next_func, task_counter=None, traceback=True):
        super().__init__()
        self.func = func
        self.next_func = next_func
        self.task_counter = task_counter
        self.traceback = traceback

    def run(self):
        try:
            result = self.func()
        except BaseException:
            if self.traceback:
                log.error(traceback.format_exc())
            to_main(self.next_func, error=sys.exc_info()[1])
        else:
            to_main(self.next_func, result=result)
        finally:
            if self.task_counter:
                self.task_counter.decrement()


class TaskCounter:
    def __init__(self):
        self.count = 0
        self.lock = threading.Lock()
        self.condition = threading.Condition(self.lock)

    def decrement(self):
        with self.lock:
            self.count -= 1
            if self.count == 0:
                self.condition.notify_all()

    def increment(self):
        with self.lock:
            self.count += 1

    def wait_for_tasks(self):
        with self.lock:
            while self.count > 0:
                self.condition.wait()


def run_task(func, next_func=None, priority=0, thread_pool=None, task_counter=None, traceback=True):
    """Schedules func to be run on a separate thread

    Args:
        func: Function to run on a separate thread.
        next_func: Callback function to run after the thread has been completed.
          The callback will be run on the main thread.
        priority: Priority for the run queue's order of execution.
        thread_pool: Instance of QThreadPool to run this task.
        task_counter: Instance of TaskCounter to count the number of tasks currently running.
        traceback: If set to true the stack trace will be logged to the error log
          if an exception was raised.
    """

    def _no_operation(*args, **kwargs):
        return

    if not next_func:
        next_func = _no_operation

    if task_counter:
        task_counter.increment()

    if not thread_pool:
        thread_pool = QCoreApplication.instance().thread_pool
    thread_pool.start(Runnable(func, next_func, task_counter, traceback), priority)


def to_main(func, *args, **kwargs):
    QCoreApplication.postEvent(QCoreApplication.instance(), ProxyToMainEvent(func, *args, **kwargs))


def to_main_with_blocking(func, *args, **kwargs):
    """Executes a command as a user-defined event, and waits until the event has
    closed before returning.  Note that any new threads started while processing
    the event will not be considered when releasing the blocking of the function.

    Args:
        func: Function to run.
    """
    _task = ProxyToMainEvent(func, *args, **kwargs)
    QCoreApplication.postEvent(QCoreApplication.instance(), _task)

    while True:
        try:
            if not _task.isAccepted():
                break
        except Exception:
            break
        time.sleep(0.01)
